#include <signal.h>
#include <string>
#include <iostream>
#include <mutex>
#include "Util/CMD.h"
#include "Util/System.h"
#include "Util/logger.h"
#include "Http/HttpRequester.h"
#include "Util/json.h"
#include "Util/DateTime.h"

using namespace std;
using namespace toolkit;

//错误类型枚举
typedef enum
{
    Err_success = 0,  //成功
    Err_eof,          //eof
    Err_timeout,      //超时
    Err_refused,      //连接被拒绝
    Err_dns,          //dns解析失败
    Err_shutdown,     //主动关闭
    Err_other = 0xFF, //其他错误
} PostErrCode;


// 设备属性
struct DevItem
{
    char devIp[20];
    int port;
    unsigned long m_ulSelfListenPort;
    char userName[64];
    char password[64];
    char id[64];

    DevItem()
    {
        memset(this, 0, sizeof(DevItem));
    }
};


class IDevCallBack
{
public:
    virtual void OnBalustraderReportStatus(int &uiStatus) = 0;
    virtual ~IDevCallBack() {}
};


class IBalustraderControl
{
public:
    virtual bool Init(const DevItem &devItem) = 0;
    virtual bool UnInit() = 0;
    virtual bool OpenBalustrader() = 0;
    virtual bool CloseBalustrader() = 0;
    virtual ~IBalustraderControl() {}
};


class HttpHelper
{
public:
    /**
     * @brief 同步POST
     * 
     * @param ip 
     * @param router 
     * @param jsonArgs 
     * @param jsonOut post返回的结果
     * @return PostErrCode枚举
     */
    int SyncPostData(const string &ip, const string &router, const string &jsonArgs, string &jsonOut)
    {
        int postRetCode = -1;
        string postRetJson = "";
        float postTimeout = 5.0f;
        bool canLeavePost = false;

        ///////////////////////////////http post///////////////////////
        //创建一个Http请求器
        HttpRequester::Ptr requesterPost(new HttpRequester());
        //使用POST方式请求
        requesterPost->setMethod("POST");
        //设置http请求头
        requesterPost->addHeader("X-Requested-With", "XMLHttpRequest");
        string origin("http://" + ip);
        requesterPost->addHeader("Origin", origin);
        //设置POST参数列表
        requesterPost->setBody(jsonArgs);

        //开启请求
        requesterPost->startRequester(
            origin + "/" + router,                    //url地址
            [&](const SockException &ex,              //网络相关的失败信息，如果为空就代表成功
                const string &status,                 //http回复的状态码，比如说200/404
                const HttpClient::HttpHeader &header, //http回复头
                const string &strRecvBody) {          //http回复body
                DebugL << "=====================HttpRequester POST==========================";

                if (ex)
                {
                    //网络相关的错误
                    WarnL << "network err:" << ex.getErrCode() << " " << ex.what();
                    postRetCode = ex.getErrCode();
                }
                else
                {
                    //打印http回复信息
                    {
                        _StrPrinter printer;
                        for (auto &pr : header)
                        {
                            printer << pr.first << ":" << pr.second << "\r\n";
                        }
                        DebugL << "status:" << status << "\r\n"
                               << "header:\r\n"
                               << (printer << endl)
                               << "\r\nbody:" << strRecvBody;
                    }

                    postRetCode = 0;
                    postRetJson = strRecvBody;
                }

                canLeavePost = true; // 可以继续
            },
            postTimeout);

        // 超时定时器保险,防止POST出现异常导致状态无法改变
        Ticker timeout;
        Timer::Ptr timer0 = std::make_shared<Timer>(
            postTimeout, [&]()
            {
                TraceL << "timeout:" << timeout.elapsedTime();
                canLeavePost = true; // 可以继续
                return false;
            },
            nullptr);

        // 阻塞等待
        while (!canLeavePost)
        {
            usleep(100 * 1000); // 休眠100毫秒
        }

        if (postRetCode == 0)
        {
            jsonOut = postRetJson;
        }

        return postRetCode;
    }
};


// 德亚栏杆机
class DevDeyaLangan : public IBalustraderControl,
                      public IDevCallBack
{
public:
    ////////////////////////////////////////////////////////////////////
    // 德亚栏杆机控制器提供http接口给客户调用，客户调用使用http POST请求，
    // 请求数据使用json字符串，设置编码格式为utf-8,设备监听端口为80
    // 协议接口列表:
    ////////////////////////////////////////////////////////////////////
    const string kRouteSetId = "lane/setid";      // 设置设备ID
    const string kRouteAlbCtrl = "lane/alb/ctrl"; // 栏杆机抬落控制
    const string kRouteOnline = "lane/online";    // 验证设备是否存活
    // const string kRouteNotice = "lane/register/notice";      // 注册消息通知地址
    // const string kRouteShow = "lane/led/show";               //一体化栏杆机LED信息显示
    // const string kRouteClear = "lane/led/clear";             //一体化栏杆机LED清屏
    // const string kRouteLightCtrl = "lane/trafficlight/ctrl"; //通行灯控制
    // const string kRouteGetDi = "lane/getdi";                 //读取gpio输入值
    // const string kRouteSetDo = "lane/setdo";                 //设置gpio输出值
    // const string kRouteGpp = "lane/gpiopulse";               //脉冲输出
    // const string kRouteReboot = "lane/reboot";               //系统重启
    const static int kIpLen = 16; // IP长度

private:
    int id;          // 设备ID
    char ip[kIpLen]; // 设备地址
    bool isOnline;   // 设备是否存活
    bool isInited;   // 设备是否初始化完毕
    bool isOpened;   // 栏杆打开
    HttpHelper httpHelper;

public:
    DevDeyaLangan() : isOnline(false), isInited(false), isOpened(false) {}
    ~DevDeyaLangan() {}

    ////////////////////////////////////////////////////////////
    // 接口实现
    ////////////////////////////////////////////////////////////

    // 初始化
    bool Init(const DevItem &item)
    {
        this->id = atoi(item.id);
        strncpy(this->ip, item.devIp, kIpLen);

        // 设置ID
        return SetId() == 0;
    }

    // 卸载
    bool UnInit()
    {
        return 0;
    }

    // 抬起
    bool OpenBalustrader()
    {
        DebugL << "抬起栏杆机id：" << this->id;

        nlohmann::json jsonArgs{
            {"action", "open"},
            {"timestamp", this->GetTimestamp()}};

        string jsonOut = "";
        if (httpHelper.SyncPostData(this->ip, kRouteAlbCtrl, jsonArgs.dump(), jsonOut) < 0)
        {
            return -1;
        }

        // TODO: 解析返回数据
        // 返回示例：
        // {
        //     "code" : 0,
        //     "message" : "success"
        // }

        // 模拟返回成功
        this->isOpened = true;
        return true;
    }

    // 关闭
    bool CloseBalustrader()
    {
        nlohmann::json jsonArgs{
            {"action", "close"},
            {"timestamp", this->GetTimestamp()}};

        string jsonOut = "";
        if (httpHelper.SyncPostData(this->ip, kRouteAlbCtrl, jsonArgs.dump(), jsonOut) < 0)
        {
            return -1;
        }

        // TODO: 解析返回数据
        // 返回示例：
        // {
        //     "code" : 0,
        //     "message" : "success"
        // }

        // 模拟返回成功
        this->isOpened = false;
        return true;
    }

    // 状态回调
    void OnBalustraderReportStatus(int &uiStatus)
    {
        uiStatus = this->GetOnline(); // 获取在线状态
    }

private:
    // 获取规定的时间格式
    const char *GetTimestamp()
    {
        auto dt = DateTime::GetNow();
        return dt.ToCString();
    }

    int SetId()
    {
        DebugL << "设置栏杆机id：" << this->id;

        nlohmann::json jsonArgs{
            {"id", this->id},
            {"timestamp", this->GetTimestamp()}};

        string jsonOut = "";
        if (httpHelper.SyncPostData(this->ip, kRouteSetId, jsonArgs.dump(), jsonOut) < 0)
        {
            return -1;
        }

        DebugL << "设置栏杆机id回调结果：" << jsonOut;

        // TODO: 解析返回数据
        // 返回示例：
        // {
        //     "code" : 0,
        //     "message" : "success"
        // }

        // 模拟返回成功
        this->isInited = true;
        return this->isInited;
    }

    /**
     * @brief 验证设备存活
     * 
     * @return int
     */
    int GetOnline()
    {
        DebugL << "栏杆机id：" << this->id << "验证设备存活";

        nlohmann::json jsonArgs{
            {"timestamp", this->GetTimestamp()}};

        string jsonOut = "";
        PostErrCode code = (PostErrCode)httpHelper.SyncPostData(this->ip, kRouteOnline, jsonArgs.dump(), jsonOut);
        if (code != 0)
        {
            return code;
        }

        DebugL << "验证设备存活回调结果：" << jsonOut;

        // TODO: 解析返回数据
        // 返回示例：
        // {
        //     "code" : 0,
        //     "message" : "success"
        // }

        // 模拟返回成功
        this->isOnline = true;
        return 0;
    }
};

#pragma region 命令行解析器
class CMD_main : public CMD
{
public:
    CMD_main()
    {
        _parser.reset(new OptionParser(nullptr));

        (*_parser) << Option('i',                 /*该选项简称，如果是\x00则说明无简称*/
                             "ip",                /*该选项全称,每个选项必须有全称；不得为null或空字符串*/
                             Option::ArgRequired, /*该选项后面必须跟值*/
                             nullptr,             /*该选项默认值*/
                             false,               /*该选项是否必须赋值，如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
                             "服务器IP",          /*该选项说明文字*/
                             nullptr);
    }

    ~CMD_main() override {}
    const char *description() const override
    {
        return "主程序命令参数";
    }
};
#pragma endregion

int main(int argc, char *argv[])
{

    //设置退出信号处理函数
    static semaphore sem;
    signal(SIGINT, [](int)
           { sem.post(); });

    // 解析命令
    CMD_main cmd_main;
    try
    {
        cmd_main.operator()(argc, argv);
    }
    catch (ExitException &)
    {
        return 0;
    }
    catch (std::exception &ex)
    {
        cout << ex.what() << endl;
        return -1;
    }

    //设置日志
    Logger::Instance().add(std::make_shared<ConsoleChannel>());
    Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());

    bool has_httpserver = cmd_main.hasKey("ip");
    if (!has_httpserver)
    {
        WarnL << "input err: ./test_dev_deyalangan -i 0.0.0.0";
        return 0;
    }

    // 测试设备的配置
    DevItem dev;
    snprintf(dev.id, sizeof(dev.id), "%d", 1);
    snprintf(dev.devIp, sizeof(dev.devIp), "%s", cmd_main["ip"].c_str());
    // 测试设备
    DevDeyaLangan deya;
    deya.Init(dev); // 初始化，设置ID

    int stateCode = 0;
    deya.OnBalustraderReportStatus(stateCode);
    sleep(6);
    deya.OpenBalustrader(); // 抬杆
    sleep(6);
    deya.CloseBalustrader(); // 落杆

    sem.wait();
    return 0;
}
